home *** CD-ROM | disk | FTP | other *** search
- /*
- File: OTVirtualClient.c
-
- Contains: This is an OpenTransport sample client application which can be used
- to exercise and test the OpenTransport Virtual Server sample.
- It also demonstrates coding techniques for OT client applications.
-
- You are welcome to use this code in any way to create you own
- OpenTransport applications. For more information on this program,
- please review the document "About OTVirtual Server".
-
- Written by: Eric Okholm
-
- Copyright: Copyright © 1999 by Apple Computer, Inc., All Rights Reserved.
-
- You may incorporate this Apple sample source code into your program(s) without
- restriction. This Apple sample source code has been provided "AS IS" and the
- responsibility for its operation is yours. You are not permitted to redistribute
- this Apple sample source code as "Apple sample source code" after having made
- changes. If you're going to re-distribute the source, we require that you make
- it clear in the source that the code was descended from Apple sample source
- code, but that you've made changes.
-
- Change History (most recent first):
- 7/22/1999 Karl Groethe Updated for Metrowerks Codewarror Pro 2.1
-
-
- */
-
- #define DoAlert(x) { sprintf(gProgramErr, x); gProgramState = kProgramError; }
- #define DoAlert1(x, y) { sprintf(gProgramErr, x, y); gProgramState = kProgramError; }
- #define DoAlert2(x, y, z) { sprintf(gProgramErr, x, y, z); gProgramState = kProgramError;}
-
- //
- // Program mode
- //
- // Before compiling,
- // set kDebugLevel to 0 for production
- // or 1 for debug code.
- //
- // In production mode, the code attempts to recover cleanly from any problems in encounters.
- // In debug mode, the unexplained phenomenon cause an alert box highlighting the situation
- // to be delivered and then the program exits.
- //
-
- #define kDebugLevel 1
-
- #if kDebugLevel > 0
-
- #define DBAlert(x) DoAlert(x)
- #define DBAlert1(x, y) DoAlert1(x, y)
- #define DBAlert2(x, y, z) DoAlert2(x, y, z)
-
- #else
-
- #define DBAlert(x) { }
- #define DBAlert1(x, y) { }
- #define DBAlert2(x, y, z) { }
-
- #endif
-
- //
- // Include files
- //
- #include <Dialogs.h>
- #include <Events.h>
- #include <Fonts.h>
- #include <GestaltEqu.h>
- #include <Memory.h>
- #include <Menus.h>
- #include <QuickDraw.h>
- #include <SegLoad.h>
- #include <stdio.h>
- #include <StdLib.h>
- #include <String.h>
- #include <strings.h>
- #include <ToolUtils.h>
- #include <Windows.h>
-
- #include <OpenTptInternet.h> // includes OpenTransport.h
- #include <OpenTptClient.h> // needed for OTReleaseBuffer()
-
- //
- // Defines, enums, resource IDs
- //
- #define kInFront (WindowPtr) -1
- #define kWindowResID 128
-
- // Apple Menu
- #define kAppleMenuResID 128
- #define kAppleMenuAbout 1
-
- // File Menu
- #define kFileMenuResID 129
- #define kFileMenuOpen 1
- #define kFileMenuClose 2
- #define kFileMenuQuit 4
-
- // Edit Menu
- #define kEditMenuResID 130 // Edit menu is disabled
-
- // Client Menu
- #define kClientMenuResID 131
- #define kClientMenuTCPPrefs 1
-
- // Alerts, etc.
- #define kAlertExitResID 128
- #define kAboutBoxResID 130
-
- // TCP Prefs Dialog
- #define kTCPPrefsDlogResID 129
- #define kServerAddrDItem 2
- #define kServerPortDItem 4
- #define kMaxConnectionsDItem 6
- #define kStartStopDItem 7
-
- // Overall program states
- enum
- {
- kProgramRunning = 0,
- kProgramDone = 1,
- kProgramError = 2
- };
-
- // Server states
- enum
- {
- kClientStopped = 0,
- kClientRunning = 1,
- kClientShuttingDown = 2
- };
-
- // Bit numbers in EPInfo stateFlags fields
- enum
- {
- kOpenInProgressBit = 0
- };
-
- // Misc stuff
- enum
- {
- kTimerIntervalInSeconds = 3,
- kTimerInterval = (kTimerIntervalInSeconds * 1000),
- kServerRequestSize = 128,
- kOTVersion111 = 0x01110000
- };
-
- // Endpoint Info Structure
-
- struct EPInfo
- {
- EndpointRef erf; // actual endpoint
- struct EPInfo* next; // used to link all acceptor's EPInfos (not atomic)
- OTLink link; // link into an OT LIFO (atomic)
- UInt8 stateFlags; // various status fields
- };
- typedef struct EPInfo EPInfo;
-
- //
- // Globals
- //
- EPInfo* gDNS = NULL;
- EPInfo* gConnectors = NULL;
- InetHostInfo gServerHostInfo;
- Boolean gWaitForServerAddr;
- int gClientState = kClientStopped;
- int gProgramState = kProgramRunning;
- char gProgramErr[128];
- DialogPtr gDialogPtr = NULL;
- WindowPtr gWindowPtr = NULL;
- long gSleepTicks = 60;
- Str255 gServerAddrStr = "\p17.202.32.195";
- long gServerAddr = 0;
- Str255 gServerPortStr = "\p2001";
- long gServerPort = 0;
- Str255 gMaxConnectionsStr = "\p100";
- long gMaxConnections = 0;
- Boolean gClientRunning = false;
- Str255 gStartStr = "\pStart";
- Str255 gStopStr = "\pStop";
- SInt32 gCntrEndpts = 0;
- SInt32 gCntrIdleEPs = 0;
- SInt32 gCntrBrokenEPs = 0;
- SInt32 gCntrPending = 0;
- SInt32 gCntrConnections = 0;
- SInt32 gCntrTotalConnections = 0;
- SInt32 gCntrBytesRcvd = 0;
- SInt32 gCntrTotalBytesRcvd = 0;
- SInt32 gCntrDiscon = 0;
- OTLIFO gIdleEPLIFO;
- OTLIFO* gIdleEPs = &gIdleEPLIFO;
- OTLIFO gBrokenEPLIFO;
- OTLIFO* gBrokenEPs = &gBrokenEPLIFO;
- OTConfiguration* gCfgMaster = NULL;
- Boolean gWaitForEventLoop = false;
- long gTimerTask = 0;
- SInt32 gCntrIntervalConnects = 0;
- SInt32 gCntrIntervalBytes = 0;
- SInt32 gConnectsPerSecond = 0;
- SInt32 gConnectsPerSecondMax = 0;
- SInt32 gKBytesPerSecond = 0;
- SInt32 gKBytesPerSecondMax = 0;
- SInt32 gCntrIntervalEventLoop = 0;
- SInt32 gEventsPerSecond = 1;
- SInt32 gEventsPerSecondMax = 1;
- Boolean gDoWindowUpdate = true;
- unsigned char gServerRequest[kServerRequestSize];
- OSType gOTVersionSelector = 'otvr';
- UInt32 gOTVersion;
-
- //
- // OpenTransport Networking Code Prototypes
- //
- static void DoConnect(EPInfo*);
- static Boolean EPClose(EPInfo*);
- static Boolean EPOpen(EPInfo*, OTConfiguration* cfg);
- static void NetEventLoop(void);
- static void NetInit(void);
- static void NetShutdown(void);
- static pascal void Notifier(void*, OTEventCode, OTResult, void*);
- static void ReadData(EPInfo*);
- static void Recycle(void);
- static void SendRequest(EPInfo*);
- static void StartClient(void);
- static void StopClient(void);
- static void TimerInit();
- static void TimerDestroy();
- static pascal void TimerRun(void*);
-
- //
- // Macintosh Program Wrapper Prototypes
- //
- static void AboutBox(void);
- static void AlertExit(char* );
- static void DialogClose(void);
- static Boolean EventDialog(EventRecord*);
- static void EventDrag(WindowPtr, Point);
- static void EventGoAway(WindowPtr, Point);
- static void EventKeyDown(EventRecord*);
- static void EventLoop(void);
- static void EventMouseDown(EventRecord*);
- static void MacInit(void);
- static void MacInitROM(void);
- static void MyC2PStr(char*, Str255);
- static void MyP2CStr(Str255, char*);
- static void MenuDispatch(long);
- static void SetupMenus(void);
- static void TCPPrefsDialog(void);
- static void TCPPrefsReset(void);
- static void WindowClose(void);
- static void WindowOpen(void);
- static void WindowUpdate(void);
-
-
- //////////////////////////////////////////////////////////////////////////////////////
- //
- // OpenTransport Networking Code
- //
- // The code in this section provides the networking portions of the
- // OpenTransport Virtual Client.
- //
- //////////////////////////////////////////////////////////////////////////////////////
-
- //
- // DoBind
- //
- // This routine requests a wildcard port binding from the transport protocol.
- // Since the program doesn't care what port is returned, it passes in NULL
- // for the bind return parameter. The bind request structure is ephemeral
- // and can be a local stack variable since OT is done with it when the call returns.
- // The bind is done when the notifier receives a T_BINDCOMPLETE event.
- //
- static void DoBind(EPInfo* epi)
- {
- OSStatus err;
- TBind bindReq;
- InetAddress inAddr;
-
- //
- // Bind the endpoint to a wildcard address
- // (assign us a port, we don't care which one).
- //
- OTInitInetAddress(&inAddr, 0, 0);
- bindReq.addr.len = sizeof(InetAddress);
- bindReq.addr.buf = (unsigned char*) &inAddr;
- bindReq.qlen = 0;
-
- err = OTBind(epi->erf, &bindReq, NULL);
- if (err != kOTNoError)
- {
- DBAlert1("DoBind: OTBind error %d", err);
- return;
- }
- }
-
- //
- // DoConnect
- //
- // This routine attempts establish a new connection to the globally known
- // server address and port. If the program is still trying to use the
- // DNR to resolve the server's host name into an IP address, the endpoint
- // is queued for later connection.
- //
- static void DoConnect(EPInfo* epi)
- {
- OSStatus err;
- TCall sndCall;
- InetAddress inAddr;
- OTLink* link;
-
- // Don't want new connections if already shutting down.
- if (gProgramState != kProgramRunning || gClientState != kClientRunning)
- return;
-
- if (gWaitForServerAddr || gWaitForEventLoop)
- {
- if (epi != NULL)
- {
- OTLIFOEnqueue(gIdleEPs, &epi->link);
- OTAtomicAdd32(1, &gCntrIdleEPs);
- }
- return;
- }
-
- // If we weren't passed a specific EPInfo, try to get an idle one.
- if (epi == NULL)
- {
- link = OTLIFODequeue(gIdleEPs);
- if (link == NULL)
- return;
- OTAtomicAdd32(-1, &gCntrIdleEPs);
- epi = OTGetLinkObject(link, EPInfo, link);
- }
-
- OTInitInetAddress(&inAddr, gServerPort, gServerAddr);
- OTMemzero(&sndCall, sizeof(TCall));
- sndCall.addr.len = sizeof(InetAddress);
- sndCall.addr.buf = (unsigned char*) &inAddr;
-
- OTAtomicAdd32(1, &gCntrPending);
- err = OTConnect(epi->erf, &sndCall, NULL);
- if (err != kOTNoDataErr)
- {
- OTAtomicAdd32(-1, &gCntrPending);
- DBAlert2("DoConnect: OTConnect error %d state %d", err, OTGetEndpointState(epi->erf));
- return;
- }
-
- //
- // If OTConnect didn't return an error, this thread will
- // resume when the notifier gets a T_CONNECT event...
- //
- }
-
- //
- // EPClose
- //
- // This routine is a front end to OTCloseProvider.
- // Centralizing closing of endpoints makes debugging and instrumentation easier.
- //
- static Boolean EPClose(EPInfo* epi)
- {
- OSStatus err;
-
- //
- // If an endpoint is still being opened, we can't close it yet.
- // There is no way to cancel an OTAsyncOpenEndpoint, so we just
- // have to wait for the T_OPENCOMPLETE event at the notifier.
- //
- if ( OTAtomicTestBit(&epi->stateFlags, kOpenInProgressBit) )
- return false;
-
- err = OTCloseProvider(epi->erf);
- epi->erf = NULL;
- if (err)
- DBAlert1("EPClose: OTCloseProvider error %d", err);
-
- if (epi != gDNS)
- OTAtomicAdd32(-1, &gCntrEndpts);
- return true;
-
- }
-
- //
- // EPOpen:
- //
- // A front end to OTAsyncOpenEndpoint.
- // A status bit is set so we know there is an open in progress.
- // It is cleared when the notifier gets a T_OPENCOMPLETE where the context
- // pointer is this EPInfo. Until that happens, this EPInfo can't be cleaned
- // up and released.
- //
- static Boolean EPOpen(EPInfo* epi, OTConfiguration* cfg)
- {
- OSStatus err;
-
- OTAtomicSetBit(&epi->stateFlags, kOpenInProgressBit);
- err = OTAsyncOpenEndpoint(cfg, 0, NULL, &Notifier, epi);
- if (err != kOTNoError)
- {
- OTAtomicClearBit(&epi->stateFlags, kOpenInProgressBit);
- DBAlert1("EPOpen: OTAsyncOpenEndpoint error %d", err);
- return false;
- }
- return true;
- }
-
- //
- // NetEventLoop
- //
- // This routine is called once during each pass through the program's event loop.
- // If the program is running on OT 1.1.2 or an earlier release, this is where
- // outbound orderly releases are started (see comments in DoSndOrderlyRelease
- // for more information on that). This is also where endpoints are "fixed" by
- // closing them and opening a new one to replace them. This is rarely necessary,
- // but works around some timing issues in OTUnbind(). Having passed through the
- // event loop once, we assume it is safe to turn off throttle-back. And, finally,
- // if we have deferred handing of a T_LISTEN, here we start it up again.
- //
- static void NetEventLoop()
- {
- Recycle();
- gWaitForEventLoop = false;
- DoConnect(NULL);
- }
-
- //
- // NetInit:
- //
- // This is nothing but a front end to InitOpenTransport.
- // The only reason for having this routine is to get the call to InitOpenTransport
- // up into the "networking" section of the program and out of the
- // "macintosh program wrapper" section of the program.
- //
- static void NetInit()
- {
- OSStatus err;
-
- err = InitOpenTransport();
- if (err)
- {
- DBAlert1("NetInit: InitOpenTransport error %d", err);
- return;
- }
- err = Gestalt(gOTVersionSelector, (long*) &gOTVersion);
- if (err || (gOTVersion < kOTVersion111))
- {
- DoAlert("Please install Open Transport 1.1.1 or later");
- return;
- }
- TimerInit();
- }
-
- //
- // NetShutdown:
- //
- // Ditto...
- //
- static void NetShutdown()
- {
- TimerDestroy();
- CloseOpenTransport();
- }
-
-
- //
- // Notifier:
- //
- // Most of the interesting networking code in this program resides inside
- // this notifier. In order to run asynchronously and as fast as possible,
- // things are done inside the notifier whenever possible. Since almost
- // everything is done inside the notifier, there was little need for specical
- // synchronization code.
- //
- // Note: The DNR events are combined with normal endpoint events in this notifier.
- // The only events which are expected from the DNR are T_DNRSTRINGTOADDRCOMPLETE
- // and T_OPENCOMPLETE.
- //
- // IMPORTANT NOTE: Normal events defined by XTI (T_LISTEN, T_CONNECT, etc)
- // and OT completion events (T_OPENCOMPLETE, T_BINDCOMPLETE, etc.) are not
- // reentrant. That is, whenever our notifier is invoked with such an event,
- // the notifier will not be called again by OT for another normal or completion
- // event until we have returned out of the notifier - even if we make OT calls
- // from inside the notifier. This is a useful synchronization tool.
- // However, there are two kinds of events which will cause the notifier to
- // be reentered. One is T_MEMORYRELEASED, which always happens instantly.
- // The other are state change events like kOTProviderWillClose.
- //
-
- static pascal void Notifier(void* context, OTEventCode event, OTResult result, void* cookie)
- {
- OSStatus err;
- OTResult epState;
- EPInfo* epi = (EPInfo*) context;
-
- //
- // Once the program is shutting down, most events would be uninteresting.
- // However, we still need T_OPENCOMPLETE and T_MEMORYRELEASED events since
- // we can't call CloseOpenTransport until all OTAsyncOpenEndpoints and
- // OTSends with AckSends have completed. So those specific events
- // are still accepted.
- //
- if (gProgramState != kProgramRunning)
- {
- if (event != T_OPENCOMPLETE)
- return;
- }
-
- //
- // This really isn't necessary, it's just a sanity check which should be removed
- // once a program is debugged. It's just making sure we don't get event notifications
- // after all of our endpoints have been closed.
- //
- if (gClientState == kClientStopped)
- {
- DBAlert1("Notifier: got event %d when client not running!", event);
- return;
- }
-
- //
- // Within the notifier, all action is based on the event code.
- // In this notifier, fatal errors all break out of the switch to the bottom.
- // As long as everything goes as expected, the case returns rather than breaks.
- //
- switch (event)
- {
- //
- // T_BINDCOMPLETE:
- //
- // This event is returned when an endpoint has been bound to a wildcard addr.
- // No errors are expected. The program immediately attempts to establish
- // a connection from this endpoint to the server.
- //
- case T_BINDCOMPLETE:
- {
- if (result != kOTNoError)
- {
- DBAlert1("Notifier: T_BINDCOMPLETE result %d", result);
- return;
- }
- DoConnect(epi); // resumes at T_CONNECT
- return;
- }
-
- //
- // T_CONNECT:
- //
- // This event is returned when a connection is established to the server.
- // The program must call OTRcvConnect() to get the conenction information
- // and clear the T_CONNECT event from the stream. Since OTRcvConnect()
- // returns immediately (rather than via a completion event to the notifier)
- // we can use local stack structures for parameters.
- //
- case T_CONNECT:
- {
- TCall call;
- InetAddress caddr;
-
- if (result != kOTNoError)
- {
- DBAlert1("Notifier: T_CONNECT result %d", result);
- return;
- }
-
- call.addr.maxlen = sizeof(InetAddress);
- call.addr.buf = (unsigned char*) &caddr;
- call.opt.maxlen = 0;
- call.opt.buf = NULL;
- call.udata.maxlen = 0;
- call.udata.buf = NULL;
-
- err = OTRcvConnect(epi->erf, &call);
- if (err != kOTNoError)
- {
- DBAlert1("Notifier: T_CONNECT - OTRcvConnect err %d", err);
- return;
- }
- OTAtomicAdd32(-1, &gCntrPending);
- OTAtomicAdd32(1, &gCntrConnections);
- OTAtomicAdd32(1, &gCntrTotalConnections);
- OTAtomicAdd32(1, &gCntrIntervalConnects);
-
- SendRequest(epi);
-
- //
- // Since we won't be sending any data,
- // might as well send along the orderly release now.
- //
- err = OTSndOrderlyDisconnect(epi->erf);
- if (err != kOTNoError)
- {
- if (err != kOTLookErr)
- {
- DBAlert1("Notifier: T_CONNECT: OTSndOrderlyDisconnect error %d", err);
- }
- }
- return; // Wait for a T_DATA...
- }
-
- //
- // T_DATA:
- //
- // The main rule for processing T_DATA's is to remember that once you have
- // a T_DATA, you won't get another one until you have read to a kOTNoDataErr.
- // The advanced rule is to remember that you could get another T_DATA
- // during an OTRcv() which will eventually return kOTNoDataErr, presenting
- // the application with a synchronization issue to be most careful about.
- //
- // In this application, since an OTRcv() calls are made from inside the notifier,
- // this particular synchronization issue doesn't become a problem.
- //
- case T_DATA:
- {
- ReadData(epi);
- return;
- }
-
- //
- // T_DNRSTRINGTOADDRCOMPLETE:
- //
- // This event occurs when the DNR has finished an attempt to translate
- // the server's name into an IP address we can use to connect to.
- //
- case T_DNRSTRINGTOADDRCOMPLETE:
- {
- if (result != kOTNoError)
- {
- DBAlert1("Notifier: T_DNRSTRINGTOADDRCOMPLETE result %d", result);
- return;
- }
- gServerAddr = gServerHostInfo.addrs[0];
- gWaitForServerAddr = false;
- return;
- }
-
- //
- // T_DISCONNECT:
- //
- // An inbound T_DISCONNECT event usually indicates that the other side of the
- // connection did an abortive disconnect (as opposed to an orderly release).
- // It also can be generated by the transport provider on the system (e.g. tcp)
- // when it decides that a connection is no longer in existance.
- //
- // We receive the disconnect, but this program ignores the associated reason (NULL param).
- // It is possible to get back a kOTNoDisconnectErr from the OTRcvDisconnect call.
- // This can happen when either (1) the disconnect on the stream is hidden by a
- // higher priority message, or (2) something has flushed or reset the disconnect
- // event in the meantime. This is not fatal, and the appropriate thing to do is
- // to pretend the T_DISCONNECT event never happened. Any other error is unexpected
- // and needs to be reported so we can fix it. Next, unbind the endpoint so we can
- // reuse it for a new inbound connection.
- //
- // It is possible to get an error on the unbind due to a bug in OT 1.1.1 and earlier.
- // The best thing to do for that is close the endpoint and open a new one to replace it.
- // We do this back in the main thread so we don't have to deal with synchronization problems.
- //
- case T_DISCONNECT:
- {
- epState = OTGetEndpointState(epi->erf);
- if (epState == T_OUTCON)
- {
- OTAtomicAdd32(-1, &gCntrPending);
- }
- OTAtomicAdd32(1, &gCntrDiscon);
- err = OTRcvDisconnect(epi->erf, NULL);
- if (err != kOTNoError)
- {
- if (err == kOTNoDisconnectErr)
- return;
- DBAlert1("Notifier: T_DISCONNECT - OTRcvDisconnect error %d", err);
- return;
- }
-
- err = OTUnbind(epi->erf);
- if (err != kOTNoError)
- {
- OTLIFOEnqueue(gBrokenEPs, &epi->link);
- OTAtomicAdd32(1, &gCntrBrokenEPs);
- }
- return;
- }
-
- //
- // T_GODATA:
- //
- // Because of the complexity involved in the implementation of OT flow control,
- // it is sometimes possible to receive a T_GODATA even when we aren't subject
- // to flow control - normally only at the start of a program. If this happens,
- // ignoring it is the correct thing to do.
- //
- case T_GODATA:
- {
- return;
- }
-
- //
- // T_OPENCOMPLETE:
- //
- // This event occurs when an OTAsyncOpenEndpoint() completes. Note that this event,
- // just like any other async call made from outside the notifier, can occur during
- // the call to OTAsyncOpenEndpoint(). That is, in the main thread the program did
- // the OTAsyncOpenEndpoint(), and the notifier is invoked before control is returned
- // to the line of code following the call to OTAsyncOpenEndpoint(). This is one
- // event we need to keep track of even if we are shutting down the program since there
- // is no way to cancel outstanding OTAsyncOpenEndpoint() calls.
- //
- case T_OPENCOMPLETE:
- {
- char serverCString[256];
-
- OTAtomicClearBit(&epi->stateFlags, kOpenInProgressBit);
- if (result == kOTNoError)
- epi->erf = (EndpointRef) cookie;
- else
- {
- DBAlert1("Notifier: T_OPENCOMPLETE result %d", result);
- return;
- }
-
- if (gProgramState != kProgramRunning)
- return;
-
- if (epi == gDNS)
- {
- MyP2CStr(gServerAddrStr, serverCString);
- err = OTInetStringToAddress((InetSvcRef)epi->erf, serverCString, &gServerHostInfo);
- if (err != kOTNoError)
- {
- //
- // Can't translate the server address string
- //
- DBAlert1("Notifier: T_OPENCOMPLETE - OTInetStringToAddress error %d", err);
- }
- return; // DNS resumes at T_DNRSTRINGTOADDRCOMPLETE
- }
- else
- {
- OTAtomicAdd32(1, &gCntrEndpts);
-
- //
- // Set to blocking mode so we don't have to deal with kEAGAIN errors.
- // Async/blocking is the best mode to write an OpenTransport application in.
- //
- err = OTSetBlocking(epi->erf);
- if (err != kOTNoError)
- {
- DBAlert1("Notifier: T_OPENCOMPLETE - OTSetBlocking error %d", err);
- return;
- }
-
- DoBind(epi);
- return; // resumes at T_BINDCOMPLETE
- }
- }
-
- //
- // T_ORDREL:
- //
- // This event occurs when an orderly release has been received on the stream.
- //
- case T_ORDREL:
- {
- err = OTRcvOrderlyDisconnect(epi->erf);
- if (err != kOTNoError)
- {
- DBAlert1("Notifier: T_ORDREL - OTRcvOrderlyDisconnect error %d", err);
- return;
- }
- epState = OTGetEndpointState(epi->erf);
- if (epState != T_IDLE)
- return;
- OTAtomicAdd32(-1, &gCntrConnections);
- err = OTUnbind(epi->erf);
- if (err != kOTNoError)
- {
- OTLIFOEnqueue(gBrokenEPs, &epi->link);
- OTAtomicAdd32(1, &gCntrBrokenEPs);
- }
- return;
- }
-
- //
- // T_UNBINDCOMPLETE:
- //
- // This event occurs on completion of an OTUnbind().
- // The endpoint is ready for reuse on a new inbound connection.
- // Put it back into the queue of idle endpoints.
- // Note that the OTLIFO structure has atomic queue and dequeue,
- // which can be helpful for synchronization protection.
- //
- case T_UNBINDCOMPLETE:
- {
- if (result != kOTNoError)
- {
- //
- // Unbind errors can occur as a result of a bug in OT 1.1.1 and earlier
- // versions. The best recovery is to put the endpoint in the broken
- // list for recycling with a clean, new endpoint.
- //
- OTLIFOEnqueue(gBrokenEPs, &epi->link);
- OTAtomicAdd32(1, &gCntrBrokenEPs);
- return;
- }
- DoBind(epi);
- return;
- }
-
-
- //
- // default:
- //
- // There are events which we don't handle, but we don't expect to see
- // any of them. When running in debugging mode while developing a program,
- // we exit with an informational alert. Later, in the production version
- // of the program, we ignore the event and try to keep running.
- //
- default:
- {
- DBAlert1("Notifier: unknown event <%x>", event);
- return;
- }
- }
- }
-
- //
- // ReadData:
- //
- // This routine attempts to read all available data from an endpoint.
- // Since this routine is only called from inside the notifier in the current
- // version of OTVirtualClient, it is not necessary to program to handle
- // getting back a T_DATA notification DURING an OTRcv() call, as would be
- // the case if we read from outside the notifier. We must read until we
- // get a kOTNoDataErr in order to clear the T_DATA event so we will get
- // another notification of T_DATA in the future.
- //
- // Currently this application uses no-copy receives to get data. This obligates
- // the program to return the buffers to OT asap. Since this program does nothing
- // with data other than count it, that's easy. Future, more complex versions
- // of this program will do more interesting things with regards to that.
- //
- static void ReadData(EPInfo* epi)
- {
- OTBuffer* bp;
- OTResult res;
- OTFlags flags;
-
- while (true)
- {
- res = OTRcv(epi->erf, &bp, kOTNetbufDataIsOTBufferStar, &flags);
-
- if (res > 0)
- {
- OTAtomicAdd32(res, &gCntrBytesRcvd);
- OTAtomicAdd32(res, &gCntrTotalBytesRcvd);
- OTAtomicAdd32(res, &gCntrIntervalBytes);
- OTReleaseBuffer(bp);
- continue;
- }
- if (res == kOTNoDataErr)
- {
- //
- // Since ReadData is only called from inside the notifier
- // we don't have to worry about having missed a T_DATA
- // during the OTRcv.
- //
- return;
- }
- if (res <= 0)
- {
- if (res == kOTLookErr)
- {
- res = OTLook(epi->erf);
- if (res == T_ORDREL)
- return;
- if (res == T_GODATA)
- {
- //
- // This isn't expected, but it has happened occasionally.
- // The correct way to proceed is to ignore it.
- //
- continue;
- }
- else
- {
- DBAlert1("ReadData: OTRcv got OTLookErr 0x%08x", res);
- }
- }
- else
- {
- DBAlert1("ReadData: OTRcv error %d", res);
- }
- }
- }
- }
-
- //
- // Recycle:
- //
- // This routine shouldn't be necessary, but it is helpful to work around both
- // problems in OpenTransport and bugs in this program. Basicly, whenever an
- // unexpected error occurs which shouldn't be fatal to the program, the EPInfo
- // is queued on the BrokenEP queue. When recycle is called, once per pass around
- // the event loop, it will attempt to close the associated endpoint and open
- // a new one to replace it using the same EPInfo structure. This process of
- // closing an errant endpoint and opening a replacement is probably the most
- // reliable way to make sure that this program and OpenTransport can recover
- // from unexpected happenings in a clean manner.
- //
- static void Recycle()
- {
- OTLink* list = OTLIFOStealList(gBrokenEPs);
- OTLink* link;
- EPInfo* epi;
-
- while ( (link = list) != NULL )
- {
- list = link->fNext;
- epi = OTGetLinkObject(link, EPInfo, link);
- if (!EPClose(epi))
- {
- OTLIFOEnqueue(gBrokenEPs, &epi->link);
- continue;
- }
- OTAtomicAdd32(-1, &gCntrBrokenEPs);
- EPOpen(epi, OTCloneConfiguration(gCfgMaster));
- }
- }
-
- //
- // SendRequest:
- //
- // Tell the OT Virtual Server we want it to send us some data.
- // For demonstration purposes, the server will wait for a 128 byte
- // "request" to come in before sending us data. It doesn't care
- // what the request looks like, it just allows us to better simulate
- // true client/server interactions.
- //
- static void SendRequest(EPInfo* epi)
- {
- OTResult res;
-
- res = OTSnd(epi->erf, gServerRequest, kServerRequestSize, 0);
-
- //
- // This is bogus and needs to add flow control.
- // The only reason we get away with it here is because flow control
- // will never happen in the first 128 bytes sent, and that is all
- // we are sending.
- //
- if (res != kServerRequestSize)
- {
- DBAlert1("SendRequest: got result %d", res);
- }
- OTAtomicAdd32(res, &gCntrIntervalBytes);
- }
-
-
- //
- // StartClient:
- //
- // Open one InetServices (DNS) object,
- // and as many connection endpoints as the program will use.
- // Start making connections as soon as the server's name is translated
- // to an IP address.
- //
- static void StartClient()
- {
- int i;
- EPInfo* epi;
- OSStatus err;
-
- gCntrEndpts = 0;
- gCntrPending = 0;
- gCntrConnections = 0;
- gCntrBrokenEPs = 0;
- gCntrTotalConnections = 0;
- gIdleEPs->fHead = NULL;
- gBrokenEPs->fHead = NULL;
- gClientState = kClientRunning;
- TCPPrefsReset();
- gWaitForServerAddr = true;
-
- //
- // Open an InternetServices so we have access to the DNR
- // to translate the server's name into an IP address (if necessary).
- //
- gDNS = (EPInfo*) NewPtr(sizeof(EPInfo));
- if (gDNS == NULL)
- {
- DBAlert("StartClient: NewPtr cannot get memory for EPInfo");
- return;
- }
- OTMemzero(gDNS, sizeof(EPInfo));
- OTAtomicSetBit(&gDNS->stateFlags, kOpenInProgressBit);
- err = OTAsyncOpenInternetServices(kDefaultInternetServicesPath, 0, Notifier, gDNS);
- if (err != kOTNoError)
- {
- OTAtomicClearBit(&gDNS->stateFlags, kOpenInProgressBit);
- DBAlert1("OTAsyncOpenInternetServices error %d", err);
- return;
- }
-
- //
- // Get memory for EPInfo structures
- //
- for (i = 0; i < gMaxConnections; i++)
- {
- epi = (EPInfo*) NewPtr(sizeof(EPInfo));
- if (epi == NULL)
- {
- DBAlert("StartClient: NewPtr cannot get memory for EPInfo");
- return;
- }
- OTMemzero(epi, sizeof(EPInfo));
- epi->next = gConnectors;
- gConnectors = epi;
- }
-
- //
- // Open endpoints which can be used for outbound
- // connections to the server.
- //
- gCfgMaster = OTCreateConfiguration("tcp");
- if (gCfgMaster == NULL)
- {
- DBAlert("StartClient: OTCreateConfiguration returned NULL");
- return;
- }
- for (epi = gConnectors; epi != NULL; epi = epi->next)
- {
- if (!EPOpen(epi, OTCloneConfiguration(gCfgMaster)))
- break;
- }
- }
-
- //
- // StopClient:
- //
- // This is where the client is shut down, either because the user clicked
- // the stop button, or because the program is exiting (error or quit).
- // The tricky part is that we can't quit while there are outstanding
- // OTAsyncOpenEndpoint calls (which can't be cancelled, by the way).
- //
- static void StopClient()
- {
- EPInfo *epi, *last;
-
- gClientState = kClientShuttingDown;
-
- //
- // First, make sure the DNS is closed.
- //
- if (gDNS != NULL)
- {
- if (!EPClose(gDNS))
- return;
- DisposePtr((char*)gDNS);
- gDNS = NULL;
- }
-
- //
- // Start closing connector endpoints.
- // While we could be rude and just close the endpoints,
- // we try to be polite and wait for all outstanding connections
- // to finish before closing the endpoints. The is a bit easier
- // on the server which won't end up keeping around control blocks
- // for dead connections which it doesn't know are dead. Alternately,
- // we could just send a disconnect, but this seems cleaner.
- //
- epi = gConnectors;
- last = NULL;
- while (epi != NULL)
- {
- if (!EPClose(epi))
- {
- // Can't close this endpoint yet, so skip it.
- last = epi;
- epi = epi->next;
- continue;
- }
- else
- {
- if (last != NULL)
- {
- last->next = epi->next;
- DisposePtr((char*)epi);
- epi = last->next;
- }
- else
- {
- gConnectors = epi->next;
- DisposePtr((char*)epi);
- epi = gConnectors;
- }
- }
- }
-
- //
- // If the list is empty now, then all endpoints have been successfully closed,
- // so the client is stopped now. At this point we can either restart it or
- // exit the program safely.
- //
- if (gConnectors == NULL)
- {
- gClientState = kClientStopped;
- gCntrEndpts = 0;
- gCntrIdleEPs = 0;
- gCntrPending = 0;
- gCntrConnections = 0;
- gCntrBrokenEPs = 0;
- gCntrTotalConnections = 0;
- gIdleEPs->fHead = NULL;
- gBrokenEPs->fHead = NULL;
- OTDestroyConfiguration(gCfgMaster);
- }
- }
-
- //
- // TimerInit
- //
- // Start up a regular timer to do housekeeping. Strictly speaking,
- // this isn't necessary, but having a regular heartbeat allows us to
- // detect if we are so busy with network notifier processing that the
- // program's event loop isn't ever firing. We want to know this so
- // we can at least allow the user to quit the program if they want to.
- //
- static void TimerInit()
- {
- gTimerTask = OTCreateTimerTask(&TimerRun, 0);
- if (gTimerTask == 0)
- {
- sprintf(gProgramErr, "TimerInit: OTCreateTimerTask returned 0");
- gProgramState = kProgramError;
- return;
- }
- OTScheduleTimerTask(gTimerTask, kTimerInterval);
- }
-
- //
- // TimerDestroy
- //
- static void TimerDestroy()
- {
- if (gTimerTask != 0)
- {
- OTCancelTimerTask(gTimerTask);
- OTDestroyTimerTask(gTimerTask);
- gTimerTask = 0;
- }
- }
-
- //
- // TimerRun
- //
- // Fires every N seconds, no matter how busy the system is.
- // We use this to detect if the program's main event loop is getting no time,
- // in which case we can slow the client down by doing a throttle-back until
- // the event loop can run at least once. It also is a convenient statistics
- // gathering point.
- //
- static pascal void TimerRun(void*)
- {
- gConnectsPerSecond = (gCntrIntervalConnects / kTimerIntervalInSeconds);
- gKBytesPerSecond = (gCntrIntervalBytes / (kTimerIntervalInSeconds * 1024));
- gEventsPerSecond = (gCntrIntervalEventLoop / kTimerIntervalInSeconds);
- if (gCntrIntervalEventLoop == 0)
- gWaitForEventLoop = true;
-
- if (gConnectsPerSecond > gConnectsPerSecondMax)
- gConnectsPerSecondMax = gConnectsPerSecond;
- if (gKBytesPerSecond > gKBytesPerSecondMax)
- gKBytesPerSecondMax = gKBytesPerSecond;
- if (gEventsPerSecond > gEventsPerSecondMax)
- gEventsPerSecondMax = gEventsPerSecond;
-
- gCntrIntervalConnects = 0;
- gCntrIntervalBytes = 0;
- gCntrIntervalEventLoop = 0;
- gDoWindowUpdate = true;
- gCntrConnections = gCntrEndpts - gCntrPending - gCntrBrokenEPs;
-
- OTScheduleTimerTask(gTimerTask, kTimerInterval);
- }
-
-
-
- //////////////////////////////////////////////////////////////////////////////////////
- //
- // Macintosh Program Wrapper
- //
- // The code from here down deals with the Macintosh environment, events,
- // menus, command keys, etc. Networking code is in the section above.
- // Since this code is fairly basic, and since this isn't really intended
- // to be a "sample Macintosh application" (just a sample OpenTransport application)
- // this section isn't heavily commented. There are much better Macintosh
- // application samples for handling mouse, keyboard, event loops, etc.
- //
- //////////////////////////////////////////////////////////////////////////////////////
-
- static void AboutBox()
- {
- Alert(kAboutBoxResID, NULL);
- }
-
- static Boolean EventDialog(EventRecord* event)
- {
- DialogPtr dp;
- short item;
- short itemType;
- Handle itemHandle;
- Rect itemRect;
-
- if (event->modifiers & cmdKey)
- {
- EventKeyDown(event); // this allows menu commands while dialog is active window
- return false; // note if I add cut/paste I will have to rework this.
- }
- if ((DialogSelect(event, &dp, &item)) && (dp == gDialogPtr))
- {
- GetDialogItem(gDialogPtr, item, &itemType, &itemHandle, &itemRect);
- switch (item)
- {
- case kServerAddrDItem:
- GetDialogItemText(itemHandle, gServerAddrStr);
- return true;
-
- case kServerPortDItem:
- GetDialogItemText(itemHandle, gServerPortStr);
- return true;
-
- case kMaxConnectionsDItem:
- GetDialogItemText(itemHandle, gMaxConnectionsStr);
- return true;
-
- case kStartStopDItem:
- GetDialogItem(gDialogPtr, kStartStopDItem, &itemType, &itemHandle, &itemRect);
- if (gClientRunning)
- {
- StopClient();
- SetControlTitle((ControlHandle)itemHandle, gStartStr);
- gClientRunning = false;
- }
- else
- {
- StartClient();
- SetControlTitle((ControlHandle)itemHandle, gStopStr);
- gClientRunning = true;
- }
- DrawDialog(gDialogPtr);
- return true;
- }
- }
- return false;
- }
-
- static void TCPPrefsReset()
- {
- StringToNum(gServerPortStr, &gServerPort);
- StringToNum(gMaxConnectionsStr, &gMaxConnections);
- }
-
- static void TCPPrefsDialog()
- {
- short itemType;
- Handle itemHandle;
- Rect itemRect;
-
- gDialogPtr = GetNewDialog(kTCPPrefsDlogResID, NULL, kInFront);
- SetWTitle(gDialogPtr, "\pTCP Preferences");
-
- GetDialogItem(gDialogPtr, kServerAddrDItem, &itemType, &itemHandle, &itemRect);
- SetDialogItemText(itemHandle, gServerAddrStr);
-
- GetDialogItem(gDialogPtr, kServerPortDItem, &itemType, &itemHandle, &itemRect);
- SetDialogItemText(itemHandle, gServerPortStr);
-
- GetDialogItem(gDialogPtr, kMaxConnectionsDItem, &itemType, &itemHandle, &itemRect);
- SetDialogItemText(itemHandle, gMaxConnectionsStr);
-
- GetDialogItem(gDialogPtr, kStartStopDItem, &itemType, &itemHandle, &itemRect);
- if (gClientRunning)
- SetControlTitle((ControlHandle)itemHandle, gStopStr);
- else
- SetControlTitle((ControlHandle)itemHandle, gStartStr);
- DrawDialog(gDialogPtr);
- }
-
- static void DialogClose()
- {
- DisposeDialog(gDialogPtr);
- gDialogPtr = NULL;
- TCPPrefsReset();
- }
-
- static void MenuDispatch(long menu)
- {
- short menuID;
- short cmdID;
-
- menuID = HiWord(menu);
- cmdID = LoWord(menu);
- switch(menuID)
- {
- case kAppleMenuResID:
- {
- switch (cmdID)
- {
- case kAppleMenuAbout:
- AboutBox();
- break;
-
- default:
- break;
- }
- break;
- }
-
- case kFileMenuResID:
- {
- switch (cmdID)
- {
- case kFileMenuQuit:
- gProgramState = kProgramDone;
- break;
-
- case kFileMenuOpen:
- WindowOpen();
- break;
-
- case kFileMenuClose:
- WindowClose();
- break;
-
- default:
- break;
- }
- break;
- }
-
- case kEditMenuResID:
- break;
-
- case kClientMenuResID:
- {
- switch (cmdID)
- {
- case kClientMenuTCPPrefs:
- TCPPrefsDialog();
- break;
-
- default:
- break;
- }
- break;
- }
- }
-
- }
-
- static void EventDrag(WindowPtr wp, Point loc)
- {
- Rect dragBounds;
-
- dragBounds = qd.screenBits.bounds;
- DragWindow(wp, loc, &dragBounds);
- }
-
- static void EventGoAway(WindowPtr wp, Point loc)
- {
- if (TrackGoAway(wp, loc))
- {
- if (wp == gWindowPtr)
- WindowClose();
- else if (wp == gDialogPtr)
- DialogClose();
- }
- }
-
- static void EventMouseDown(EventRecord* event)
- {
- short part;
- WindowPtr wp;
- long menu;
-
- part = FindWindow(event->where, &wp);
- switch (part)
- {
- case inMenuBar:
- menu = MenuSelect(event->where);
- HiliteMenu(0);
- MenuDispatch(menu);
- break;
-
- case inDrag:
- EventDrag(wp, event->where);
- break;
-
- case inGoAway:
- EventGoAway(wp, event->where);
- break;
-
- case inContent:
- SelectWindow(wp);
- break;
-
- case inGrow: // no grow box
- case inZoomIn: // no zoom box
- case inZoomOut: // no zoom box
- case inSysWindow:
- case inDesk:
- default:
- break;
- }
- }
-
- static void EventKeyDown(EventRecord* event)
- {
- char c;
- long menu;
-
- c = event->message & charCodeMask;
- if (event->modifiers & cmdKey)
- {
- // cmd key
- menu = MenuKey(c);
- HiliteMenu(0);
- if (menu != 0)
- MenuDispatch(menu);
- }
- else
- {
- // normal keystroke
- }
- }
-
- static void EventLoop()
- {
- EventRecord event;
-
- while ((gProgramState == kProgramRunning) || (gClientState != kClientStopped))
- {
- OTAtomicAdd32(1, &gCntrIntervalEventLoop);
- if (WaitNextEvent(everyEvent, &event, gSleepTicks, 0))
- {
- if ((gDialogPtr != NULL) && (IsDialogEvent(&event)))
- {
- if (EventDialog(&event))
- continue;
- }
- switch (event.what)
- {
- case keyDown:
- EventKeyDown(&event);
- break;
-
- case mouseDown:
- EventMouseDown(&event);
- break;
-
- case updateEvt:
- // redraw window now
- break;
-
- case activateEvt:
- // activate or deactivate window controls
- break;
-
- case mouseUp:
- case keyUp:
- case autoKey:
- case diskEvt:
- case app4Evt:
- default:
- break;
- }
- }
-
- if (((gProgramState == kProgramRunning) && (gClientState == kClientShuttingDown)) ||
- ((gProgramState != kProgramRunning) && (gClientState != kClientStopped)))
- StopClient();
- else if ((gProgramState == kProgramRunning) && (gClientState == kClientRunning))
- NetEventLoop();
- WindowUpdate();
- }
- }
-
- static void WindowClose()
- {
- if (gWindowPtr == NULL)
- return;
- DisposeWindow(gWindowPtr);
- gWindowPtr = NULL;
- }
-
- static void WindowOpen()
- {
- if (gWindowPtr != NULL)
- return;
- gWindowPtr = GetNewWindow(kWindowResID, NULL, kInFront);
- SetWTitle(gWindowPtr, "\pOTVirtualClient");
- }
-
- static void WindowUpdate()
- {
- char gStrBuf[128];
- int len;
-
- if (gWindowPtr == NULL)
- return;
-
- if (gDoWindowUpdate == false)
- return;
- gDoWindowUpdate = false;
-
- SetPort(gWindowPtr);
- EraseRgn(gWindowPtr->visRgn);
-
- gCntrConnections = gCntrEndpts - gCntrIdleEPs - gCntrPending - gCntrBrokenEPs;
-
- MoveTo(20, 20);
- sprintf(gStrBuf, "EPs: total %d idle %d", gCntrEndpts, gCntrIdleEPs);
- len = strlen(gStrBuf) ;
- DrawText(gStrBuf, 0, len);
-
- MoveTo(20, 40);
- sprintf(gStrBuf, "Connects: current %d total %d", gCntrConnections, gCntrTotalConnections);
- len = strlen(gStrBuf) ;
- DrawText(gStrBuf, 0, len);
-
- MoveTo(20, 60);
- sprintf(gStrBuf, "Pending connections %d", gCntrPending);
- len = strlen(gStrBuf) ;
- DrawText(gStrBuf, 0, len);
-
- MoveTo(20, 80);
- sprintf(gStrBuf, "KBytes received %d", (gCntrTotalBytesRcvd / 1024));
- len = strlen(gStrBuf) ;
- DrawText(gStrBuf, 0, len);
-
- MoveTo(20, 100);
- sprintf(gStrBuf, "Conn/sec: current %d max %d", gConnectsPerSecond, gConnectsPerSecondMax);
- len = strlen(gStrBuf) ;
- DrawText(gStrBuf, 0, len);
-
- MoveTo(20, 120);
- sprintf(gStrBuf, "KBy/sec: current %d max %d", gKBytesPerSecond, gKBytesPerSecondMax);
- len = strlen(gStrBuf) ;
- DrawText(gStrBuf, 0, len);
-
- MoveTo(20, 140);
- sprintf(gStrBuf, "Events/sec: %d/%d", gEventsPerSecond, gEventsPerSecondMax);
- len = strlen(gStrBuf) ;
- DrawText(gStrBuf, 0, len);
-
- MoveTo(20, 160);
- sprintf(gStrBuf, "Running at %d%% of capacity.",
- (100 - ((100 * gEventsPerSecond)/gEventsPerSecondMax)));
- len = strlen(gStrBuf) ;
- DrawText(gStrBuf, 0, len);
-
- MoveTo(20, 180);
- sprintf(gStrBuf, "Disconnects %d", gCntrDiscon);
- len = strlen(gStrBuf) ;
- DrawText(gStrBuf, 0, len);
-
- }
-
-
- static void SetupMenus()
- {
- MenuHandle mh;
- mh = GetMenu(kAppleMenuResID);
- AppendResMenu( mh, 'DRVR' ); /* Add DA list */
- InsertMenu(mh, 0);
- mh = GetMenu(kFileMenuResID);
- InsertMenu(mh, 0);
- mh = GetMenu(kEditMenuResID);
- InsertMenu(mh, 0);
- mh = GetMenu(kClientMenuResID);
- InsertMenu(mh, 0);
- DrawMenuBar();
- }
-
- static void MyC2PStr(char* cstr, Str255 pstr)
- {
- //
- // Converts a C string to a Pascal string.
- // Truncates the string if longer than 254 bytes.
- //
- int i, j;
-
- i = strlen(cstr);
- if (i > 254)
- i = 254;
- pstr[0] = i;
- for (j = 1; j <= i; j++)
- pstr[j] = cstr[j-1];
- }
-
- static void MyP2CStr(Str255 pstr, char* cstr)
- {
- int i;
-
- for (i = 0; i < pstr[0]; i++)
- cstr[i] = pstr[i+1];
- cstr[i] = 0;
- }
-
- static void AlertExit(char* err)
- {
- Str255 pErr;
-
- MyC2PStr(err, pErr);
- ParamText(pErr, NULL, NULL, NULL);
- Alert(kAlertExitResID, NULL);
- ExitToShell();
- }
-
- static void MacInitROM()
- {
- MaxApplZone();
- MoreMasters();
- InitGraf(&qd.thePort);
- InitCursor();
- InitFonts();
- InitWindows();
- InitMenus();
- TEInit();
- InitDialogs(NULL);
- FlushEvents(everyEvent, 0);
- }
-
- static void MacInit()
- {
- MacInitROM();
- WindowOpen();
- SetupMenus();
- }
-
- static void MiscInit()
- {
- int i;
-
- // This is just so the data is a little better than random for tracing
- for (i = 0; i < kServerRequestSize; i++)
- gServerRequest[i] = i;
- }
-
- void main()
- {
- MacInit();
- NetInit();
- MiscInit();
- EventLoop();
- NetShutdown();
- if (gProgramState == kProgramError)
- AlertExit(gProgramErr);
- }
-